<?php

class RunMode{
    const DEV = 0;
    const PRODUCT = 1;
}

/**
 * User: yancheng (cheng@love.xiaoyan.me)
 * Date: 14-9-16
 * Time: 下午3:49
 */
class RouterCore
{
    private static $_instance;

    private static $router = null;

    private $init = false;
    private $conf;
    private $mode = RunMode::PRODUCT; //1:product,0:dev
    private $pathInfo = '/';
    private $routerList = array();
    private $routerListFile = 'routers.data.php';
    private $controllerModule = 'controllers';
    private $twig = null;
    private $tmpt = null;
    private $filter = array();

    public static function getRouter()
    {
        if (self::$router != null) {
            return self::$router;
        }
        self::$router = new Router();
        return self::$router;
    }

    public function getTwig(){
        return $this->twig;
    }

    public function beforeInterceptor($func){
        $this->filter[] = $func;
        return $this;
    }

    public function setModule($moduleDir)
    {
        $this->controllerModule = $moduleDir;
        return $this;
    }

    /**
     * @param $mode RunMode
     * @return $this
     */
    public function setRunMode( $mode)
    {
        if($mode != RunMode::DEV && $mode != RunMode::PRODUCT){
            throw new YCRException('not support runtimes');
        }
        $this->mode = $mode;
        if($this->isDev()){
            header("Access-Control-Allow-Origin: *");
        }else{
            error_reporting(E_ERROR);
        }
        return $this;
    }

    public function isDev(){
        return $this->mode == RunMode::DEV;
    }

    static function getInstance()
    {
        if (self::$_instance != null) {
            return self::$_instance;
        }
        self::$_instance = new RouterCore();
        return self::$_instance;
    }

    private function RouterCore()
    {
        $this->initPathInfo();
    }

    /**
     * 获取当前访问路径
     * @return string
     */
    public function getPath(){
        return $this->pathInfo;
    }
    public function init($config = null)
    {
        if ($config) {
            if(is_string($config)){
                if(!file_exists(APP_DIR.$config)) $this->error(0,new YCRException('Config Not Found'));
                $config = include(APP_DIR.$config);
            }
            if (isset($config['initTemplate']) && $config['initTemplate']) {
                if (file_exists($config['initTemplate'])) {
                    $this->tmpt = new TemplateCore();
                    $this->twig = $this->tmpt->initEnv($config['initTemplate'],$this->mode);
                    initMore();
                }
            }
            $this->conf = $config;
        }
        return $this;
    }

    private function initRouter()
    {
        if ($this->mode == 1) {
            if (file_exists($this->routerListFile)) {
                $this->routerList = include($this->routerListFile);
            }
        } else {
            $this->routerList = self::getRouter()->getRouteList($this->controllerModule);
        }
    }

    private function initPathInfo()
    {
        $reqUri = $_SERVER['REQUEST_URI'];
        if (strpos($reqUri, '?')) {
            $reqUri = substr($reqUri, 0, strpos($reqUri, '?'));
        }
        $fileName = $_SERVER['SCRIPT_NAME'];
        if ($fileName == '/') {
            $fileName = '';
        }
        $this->pathInfo = substr($reqUri, strlen(dirname($fileName)));
        if(!str_startwith('/',$this->pathInfo))  $this->pathInfo = '/'.$this->pathInfo;
    }

    public function invoke($function,$params = null){
        call_user_func($function,$params);
        return $this;
    }

    public function dispatch()
    {
        if(!$this->init) $this->init();
        $this->initRouter();
        $router = $this->getMatchRouter();
        if ($router) {
            if ($router['function'] instanceof ReflectionMethod) {
                $cls = $router['function']->getDeclaringClass()->newInstance();
                $method = new ReflectionMethod($cls,'__setTwig');
                $method->invoke($cls,$this->tmpt);//默认初始化__setTwig方法
                try{
                    $this->doFilter();
                    $router['function']->invokeArgs($cls, $router['pathParams']);
                }catch (Exception $e){
                    $this->error($e->getCode(),$e);
                }
            } else {
                $this->doFilter();
                call_user_func_array($router['function'], $router['pathParams']);
            }
        }else{
            $this->error(404,new RouterException("Not Page Found on "));
        }
    }

    private function doFilter(){
        foreach($this->filter as $f){
            call_user_func($f);
        }
    }

    private function send_http_status($code) {
        $_status = array(
            // Success 2xx
            200 => 'OK',
            // Redirection 3xx
            301 => 'Moved Permanently',
            302 => 'Moved Temporarily ',  // 1.1
            // Client Error 4xx
            400 => 'Bad Request',
            403 => 'Forbidden',
            404 => 'Not Found',
            // Server Error 5xx
            500 => 'Internal Server Error',
            503 => 'Service Unavailable',
        );
        if(isset($_status[$code])) {
            header('HTTP/1.0 '.$code.' '.$_status[$code]);
            // 确保FastCGI模式下正常
            header('Status:'.$code.' '.$_status[$code]);
        }
    }

    private function error($code,$e){
        if(isAjax()){
            ob_clean();
			$error_msg = $e->getMessage();
			if($this->mode == RunMode::PRODUCT){$error_msg='';}
            ajaxResponse(500,'access resource was bad',$error_msg);
        }
        if($this->mode == RunMode::PRODUCT) ob_clean();
        $this->send_http_status($code);
        $trace = $e->getTrace();
        if(!isset($trace[0]['file'])){
            $trace[0]['file'] = $e->getFile();
            $trace[0]['line'] = $e->getLine();
        }
        $traceInfo = '';
        $time = date("Y-m-d H:i:s");
        $len = strlen(APP_DIR);
        foreach($trace as $t) {
            if($t){
                $traceInfo .= '['.$time.'] '.substr($t['file'],$len+1).' ('.$t['line'].') ';
                $traceInfo .= $t['class'].$t['type'].$t['function'].'(';
                if(is_array($t['args'])){
                    $args = array();
                    foreach($t['args'] as $arg){
                        if(is_string($arg)){
                            $args[] = $arg;
                        }else if(is_object($arg)){
                            if($arg instanceof Controller) {
                                $args[] =get_class($arg);
                            }else{
                                $args[] = gettype($arg);
                            }
                        }else if(is_array($arg)){
                           // print_r($arg);
                            //TODO
                            //$args[] = 'Array('.implode(', ', $arg).')';
                        }
                    }
                    $traceInfo .= implode(', ', $args);
                }
                $traceInfo .= ")<br />\n";
            }
        }
        $message = $e->getMessage();
        if($this->hasConf('error')
            && $this->hasConf($code,$this->conf['error'])
            && file_exists($this->conf['error'][$code])){
            include($this->conf['error'][$code]);
        }else{
            if($this->mode == RunMode::PRODUCT){
                echo $message;
            }else{
                $subMessage = $traceInfo;
                include LIB_DIR.'/page/Error.php';
            }
            exit;
        }
    }

    private function hasConf($key,$confs = null){
        if(!$confs){
            $confs = $this->conf;
        }
        return isset($confs[$key]);
    }

    public function getConf($key){
        if($this->hasConf($key)){
            return $this->conf[$key];
        }
        return null;
    }

    private function getMatchRouter()
    {
        $path = $this->pathInfo;
        //构建首页路径
        if( isset($this->conf['default_page']) && $this->conf['default_page'] && $path == "/"){
            $path = $this->conf['default_page'];
        }
        if (isset($this->routerList[$path])) {
            return array(
                'function' => $this->routerList[$path],
                'pathParams' => array()
            );
        } else {
            foreach ($this->routerList as $req => $func) {
                $req = str_replace('/', '\/', $req);
                $reqRegex = preg_replace('/{([A-Za-z_-]+)}/', "([A-Za-z0-9_-]+)", $req);
                if (preg_match_all('/{([A-Za-z_-]+)}/', $req, $rem)) {
                    $paramsList = $rem[1];
                    if (preg_match('/' . $reqRegex . '/', $path, $pathParams)) {
                        array_shift($pathParams);
                        $router = array(
                            'function' => $func,
                            'pathParams' => array()
                        );
                        $functionNeedParam = array();
                        foreach ($pathParams as $index => $p) {
//                            $this->getParam($functionNeedParam,$paramsList[$index],$p);
                            $functionNeedParam[$paramsList[$index]] = $p;
                        }
                        $ref = $router['function'];

                        if (!($router['function'] instanceof ReflectionMethod)) {
                            $ref = new ReflectionFunction($router['function']);
                        }
                        $params = $ref->getParameters();
                        foreach ($params as $functionParam) {
                            $paramsName = $functionParam->getName();
                            $optional = $functionParam->isOptional();
                            if(!$this->getParam($router['pathParams'],$paramsName)){
                                //检测特殊参数
                                if (isset($functionNeedParam[$paramsName])) {
                                    $router['pathParams'][$paramsName] = $functionNeedParam[$paramsName];
                                } else {
                                    if (!$optional) {
                                        throw new Exception('need param ' . $paramsName);
                                    }
                                    $router['pathParams'][$paramsName] = $functionParam->getDefaultValue();
                                }
                            }
                        }
                        return $router;
                    }
                }
            }
        }
        return null;
    }

    private function getParam(&$params,$name){
        if($name == 'render'){
            $params[$name] = $this->twig;
            return true;
        }
        return false;
    }

    public function getTmpl(){
        return $this->tmpt;
    }
}